﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using TheLogWatcher.Properties;
using System.Collections;
using System.Collections.Specialized;

namespace TheLogWatcher
{
    public class ColumnManager
    {
        public ColumnManager(ListView listView, string logName)
        {
            this.listView = listView;
            this.logName = logName;
            loadColumns();
            redrawColumns(-1);
        }

        private ListView listView;
        private string logName;
        private List<ColumnOption> columns = new List<ColumnOption>();

        /// <summary>
        ///    Load column settings from properties xml file into internal column manager.
        /// </summary>
        private void loadColumns()
        {
            // Find predefined logformat
            LogFormat logFormat = null;
            foreach (LogFormat item in PredefinedLogFormats.Formats)
            {
                if (item.Name.Equals(logName))
                {
                    logFormat = item;
                    break;
                }
            }
            if (logFormat == null)
            {
                Console.WriteLine("loadColumns: Cannot find used predefined log format.");
                return;
            }

            Dictionary<string, int[]> widths = deserializeWidths();
            this.columns = new List<ColumnOption>();
            if (widths.ContainsKey(logName) && widths[logName] != null)
            {
                int[] values = widths[logName];
                if (values.Length > 0)
                {
                    Dictionary<string, bool[]> visibilities = checkVisibilities(values.Length);
                    for (int i = 0; i < values.Length; i++)
                    {
                        ColumnOption col = new ColumnOption();
                        col.Width = values[i];
                        col.Visible = visibilities[logName][i];
                        col.Title = logFormat.Parts[i].Title;
                        this.columns.Add(col);
                    }
                }
            }
            else
            {
                // If no settings can be found in properties, take log format standard
                foreach (LogFormat.Part part in logFormat.Parts)
                {
                    ColumnOption col = new ColumnOption();
                    col.Width = part.StandardWidth;
                    col.Visible = true;
                    col.Title = part.Title;
                    this.columns.Add(col);
                }
                checkVisibilities(logFormat.Parts.Count);
            }
        }

        private Dictionary<string,bool[]> checkVisibilities(int columns)
        {
            Dictionary<string, bool[]> visibilities = deserializeVisibilities();
            if (!visibilities.ContainsKey(this.logName) || visibilities[logName] == null)
            {
                /*
                 * If column visibilities have not been saved yet
                 * create new definitions with initially full visible.
                 */
                bool[] colVis = new bool[columns];
                for (int i = 0; i < columns; i++)
                {
                    colVis[i] = true;
                }
                visibilities[this.logName] = colVis;
                StringCollection serialized = serializeVisibilities(visibilities);
                Settings.Default.ColumnVisibility = serialized;
                Settings.Default.Save();
            }
            
            return visibilities;
        }

        /// <summary>
        ///     Saves all internally stored column definitions to the user wide properties xml.
        /// </summary>
        private void saveColumns()
        {
            if (this.columns != null && this.columns.Count > 0)
            {
                Dictionary<string, int[]> widths = deserializeWidths();
                Dictionary<string, bool[]> visibilities = checkVisibilities(widths.Keys.Count);
                int[] colWidths = new int[this.columns.Count];
                bool[] colVis = new bool[this.columns.Count];
                for (int i = 0; i < this.columns.Count; i++)
                {
                    colWidths[i] = this.columns[i].Width;
                    colVis[i] = this.columns[i].Visible;
                }
                widths[this.logName] = colWidths;
                visibilities[this.logName] = colVis;
                Settings.Default.ColumnWidths = serializeWidths(widths);
                Settings.Default.ColumnVisibility = serializeVisibilities(visibilities);
                Settings.Default.Save();
            }
            else
            {
                Console.WriteLine("saveColumns: No column definitions existing, something wrong ?");
            }
        }

        private string divider = "#~#";

        private StringCollection serializeWidths(Dictionary<string, int[]> widths)
        {
            StringCollection serialized = new StringCollection();
            foreach (string item in widths.Keys)
            {
                StringBuilder entry = new StringBuilder(item);
                entry.Append(divider);
                int[] values = widths[item];
                if (values == null)
                {
                    Console.WriteLine("serializeWidths: Element <" + item + "> doesn't contain any values.");
                    continue;
                }
                for (int i = 0; i < values.Length; i++)
                {
                    if (i>0)
                    {
                        entry.Append(',');
                    }
                    entry.Append(values[i].ToString());
                }
                serialized.Add(entry.ToString());
            }
            return serialized;
        }

        private Dictionary<string, int[]> deserializeWidths()
        {
            Dictionary<string, int[]> values = new Dictionary<string, int[]>();
            StringCollection widths = Settings.Default.ColumnWidths;
            if (widths != null)
            {
                foreach (string item in widths)
                {
                    // Split string into key and value array
                    string[] splitted = item.Split(new string[] { divider }, StringSplitOptions.None);
                    if (splitted.Length != 2)
                    {
                        Console.WriteLine("deserializeWidths: Invalid entry: " + item);
                        continue;
                    }
                    string key = splitted[0];
                    string[] valueArray = splitted[1].Split(new char[] { ',' }, StringSplitOptions.None);
                    int[] value = new int[valueArray.Length];
                    for (int i = 0; i < valueArray.Length; i++)
                    {
                        value[i] = int.Parse(valueArray[i]);
                    }
                    values.Add(key, value);
                }
            }
            return values;
        }

        private StringCollection serializeVisibilities(Dictionary<string, bool[]> visibilities)
        {
            StringCollection serialized = new StringCollection();
            foreach (string item in visibilities.Keys)
            {
                StringBuilder entry = new StringBuilder(item);
                entry.Append(divider);
                bool[] values = visibilities[item];
                if (values == null)
                {
                    Console.WriteLine("serializeVisibilities: Element <" + item + "> doesn't contain any values.");
                    continue;
                }
                for (int i = 0; i < values.Length; i++)
                {
                    if (i > 0)
                    {
                        entry.Append(',');
                    }
                    entry.Append(values[i].ToString());
                }
                serialized.Add(entry.ToString());
            }
            return serialized;
        }

        private Dictionary<string, bool[]> deserializeVisibilities()
        {
            Dictionary<string, bool[]> values = new Dictionary<string, bool[]>();
            StringCollection visibilities = Settings.Default.ColumnVisibility;
            if (visibilities != null)
            {
                foreach (string item in visibilities)
                {
                    // Split string into key and value array
                    string[] splitted = item.Split(new string[] { divider }, StringSplitOptions.None);
                    if (splitted.Length != 2)
                    {
                        Console.WriteLine("deserializeVisibilities: Invalid entry: " + item);
                        continue;
                    }
                    string key = splitted[0];
                    string[] valueArray = splitted[1].Split(new char[] { ',' }, StringSplitOptions.None);
                    bool[] value = new bool[valueArray.Length];
                    for (int i = 0; i < valueArray.Length; i++)
                    {
                        value[i] = bool.Parse(valueArray[i]);
                    }
                    values.Add(key, value);
                }
            }
            return values;
        }

        /// <summary>
        ///     All listview columns are refreshed with the new settings
        ///     of the internally column manager.
        /// </summary>
        public void redrawColumns()
        {
            redrawColumns(-1);
        }

        public bool isVisible(string name, int index)
        {
            if (this.columns != null && this.columns.Count > index)
            {
                return this.columns[index].Visible;
            }
            return false;
        }

        /// <summary>
        ///     The listview columns are refreshed with the new settings
        ///     of the internally column manager.
        /// </summary>
        /// <param name="position">If a single position is given (>= 0) this will be refreshed only</param>
        public void redrawColumns(int position)
        {
            if (this.columns != null && this.columns.Count > 0)
            {
                if (position == -1)
                {
                    // At first clear current columns
                    listView.Columns.Clear();
                }
                for (int i = 0; i < this.columns.Count; i++)
                {
                    /* 
                     * The column header is filled when all columns
                     * have to be refreshed or the right single column
                     * was found.
                     */
                    if (position == -1 || position == i)
                    {
                        if (listView.Columns.Count == i)
                        {
                            listView.Columns.Add(this.columns[i].Title, this.columns[i].getRealWidth());
                        }
                        else
                        {
                            listView.Columns[i].Width = this.columns[i].getRealWidth();
                            listView.Columns[i].Text = this.columns[i].Title;
                        }
                    }
                    if (position == i)
                    {
                        // The only entry to refresh has been found
                        break;
                    }
                }
            }
            else
            {
                Console.WriteLine("redrawColumns: No column definitions existing, something wrong ?");
            }
        }

        /// <summary>
        ///     Changes the width of a single column.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="width"></param>
        public void changeWidth(int position, int width)
        {
            if (this.columns != null && this.columns.Count > position)
            {
                int oldWidth = this.columns[position].Width;

                if (width == 0)
                {
                    Dictionary<string, bool[]> visibilities = deserializeVisibilities();
                    visibilities[this.logName][position] = false;
                }
                else if (oldWidth == 0 && width > 0)
                {
                    Dictionary<string, bool[]> visibilities = deserializeVisibilities();
                    visibilities[this.logName][position] = true;
                }

                this.columns[position].Width = width;

                // Redraw elements
                redrawColumns(position);

                // Save changes
                saveColumns();
            }
            else
            {
                Console.WriteLine("changeWidth: No column definitions existing, something wrong ?");
            }
        }

        /// <summary>
        ///     The width has already been changed in listview.
        ///     Synchronizes a single column's width with the column manager.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="width"></param>
        public void changedWidth(int position)
        {
            if (this.columns != null && this.columns.Count > position)
            {
                int oldWidth = this.columns[position].Width;
                int newWidth = this.listView.Columns[position].Width;
                if (oldWidth > 0 && newWidth == 0)
                {
                    this.columns[position].Visible = false;
                }
                else if (newWidth > 0)
                {
                    this.columns[position].Visible = true;
                    this.columns[position].Width = newWidth;
                }
                else
                {
                    this.columns[position].Width = newWidth;
                }

                // Save changes
                saveColumns();
            }
            else
            {
                Console.WriteLine("changedWidth: No column definitions existing, something wrong ?");
            }
        }

        /// <summary>
        ///     Changes the visibility of a single column.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="visible"></param>
        public void changeVisibility(int position, bool visible)
        {
            if (this.columns != null && this.columns.Count > position)
            {
                this.columns[position].Visible = visible;

                // Redraw elements
                redrawColumns(position);

                // Save changes
                saveColumns();
            }
            else
            {
                Console.WriteLine("changeVisibility: No column definitions existing, something wrong ?");
            }
        }

        /// <summary>
        ///     Changes the position of a single column.
        /// </summary>
        /// <param name="initialPos"></param>
        /// <param name="destinationPos"></param>
        public void changePosition(int initialPos, int destinationPos)
        {
            if (this.columns != null && this.columns.Count > 0)
            {
                ColumnOption temp = this.columns[initialPos];
                this.columns.RemoveAt(initialPos);
                this.columns.Insert(destinationPos, temp);

                // Redraw elements
                redrawColumns();

                // Save changes
                saveColumns();
            }
            else
            {
                Console.WriteLine("changePosition: No column definitions existing, something wrong ?");
            }
        }
    }
}
